package ua.in.iticket.addressbook.storage;

import ua.in.iticket.addressbook.person.Address;
import ua.in.iticket.addressbook.person.Person;
import ua.in.iticket.addressbook.person.PhoneNumber;

import java.io.*;
import java.util.*;

/**
 * Created by ivanov-av
 * 12.02.14 8:46.
 */
public class FileStorage implements Storage {
    private static FileStorage storage;
    private static File inFile;
    private Integer pid;
    private boolean fileInUse = false;
    private Map<Integer, Person> personMap;

    public static FileStorage getStorage() throws StorageException {
        return getStorage(FileStorageConfig.filePath);
    }

    public static FileStorage getStorage(String file) throws StorageException {
        File tmpFile = new File(file);
        if ((storage == null)) {
            storage = new FileStorage(file);
        }
        if (!tmpFile.equals(inFile)) {
            throw new StorageException("More data source is not allowed! (now used " + inFile.getAbsoluteFile() +")");
        }
        return storage;
    }

    private FileStorage(String file) throws StorageException {
        String filePath = file.replace("\\", "/");
        inFile = new File(filePath);
        personMap = new HashMap<Integer, Person>();
        pid = 0;
        if (! inFile.exists()) {
            try {
                Writer output = new BufferedWriter(new FileWriter(inFile));
                output.close();
            } catch (IOException e) {
                throw new StorageException("File corrupted.");
            }
        }
        try {
            readFile();
        } catch (IndexOutOfBoundsException e) {
            throw new StorageException("File corrupted.");
        }
    }

    @Override
    public void commit() {
        try {
            saveFile();
        } catch (StorageException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void save(Person person) {
            if (person.getPid() == -1) {
                person.setPid(this.pid);
                this.pid++;
        }
        try {
        personMap.put((Integer) person.getPid(), person);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean delete(Person person) {
        boolean ret = false;
        if (person.getPid() != -1) {
            personMap.remove(person.getPid());
            ret = true;
        }
        return ret;
    }

    @Override
    public List<Person> getAll() {
        return new LinkedList<Person>(personMap.values());
    }

    @Override
    public List<Person> getByName(String name) {
        LinkedList<Person> list = new LinkedList<Person>();
        List<Person> all = getAll();
        Iterator<Person> iterator = all.iterator();
        while (iterator.hasNext()) {
            Person person = iterator.next();
            if (person.getFullName().toUpperCase().indexOf(name.toUpperCase()) != -1) {
                list.add(person);
            }
        }
        return list;
    }

    protected void readFile() throws StorageException, IndexOutOfBoundsException {
        if(! this.fileInUse) {
            this.fileInUse = true;
            if (!inFile.canRead()) {
                throw new StorageException("Access Denied. " + inFile.getAbsolutePath());
            }
            try {
                BufferedReader reader = new BufferedReader(new FileReader(inFile));
                try {
                    String line;
                    //Person person = new Person();
                    this.pid = 0;
                    String[] buffer = new String[0];
                    while ((line = reader.readLine()) != null) {
                        String[] array = line.split(FileStorageConfig.DELIMITER);
                        if (line.isEmpty()) {
                            Person person = PersonCoder.personDecode(buffer);
                            person.setPid(pid);
                            personMap.put(person.getPid(), person);
                            pid++;
                            buffer = new String[0];
                        } else {
                            buffer = PersonCoder.addToBuffer(buffer, line);
                        }
                    }
                } catch (IOException e) {
                    throw new StorageException("IO error" + inFile.getAbsolutePath(), e);
                } finally {
                    try {
                        reader.close();
                    } catch (IOException e) {
                        throw new StorageException("IO error" + inFile.getAbsolutePath(), e);
                    }
                }
            } catch (FileNotFoundException e) {
                throw new StorageException("File not found " + inFile.getAbsolutePath(), e);
            }
            this.fileInUse = false;
        } else {
            throw new StorageException("File locked");
        }
    }

    protected void saveFile() throws StorageException {
        if(! this.fileInUse) {
            this.fileInUse = true;
            if (inFile.exists() && !inFile.canWrite()) {
                throw new StorageException("Cannot write to file");
            }
            try{
                Writer output = new BufferedWriter(new FileWriter(inFile));
                try {
                    //output.write(System.lineSeparator());
                    for (Map.Entry<Integer, Person> personEntry : personMap.entrySet()) {
                        output.write(PersonCoder.personEncode(personEntry.getValue()));
                    }
                } catch (IOException e) {
                    throw new StorageException("IO exception",e);
                } finally {
                    try {
                        output.close();
                    } catch (IOException e) {
                        throw new StorageException("IO exception" + inFile.getAbsolutePath(), e);
                    }
                }
            } catch (IOException e) {
                throw new StorageException("IO exception",e);
            }
            this.fileInUse = false;
        } else {
            throw new StorageException("File locked");
        }
    }
}
